<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RocketMQ消费位移(Offset)详解 - 技术小馆</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #333;
            line-height: 1.6;
        }
        h1, h2, h3, h4 {
            font-family: 'Noto Serif SC', serif;
            font-weight: 600;
        }
        .hero {
            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
        }
        .code-block {
            background-color: #2d3748;
            border-left: 4px solid #4f46e5;
        }
        .feature-card {
            transition: all 0.3s ease;
        }
        .feature-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
        }
        .highlight {
            position: relative;
        }
        .highlight:before {
            content: '';
            position: absolute;
            left: 0;
            bottom: 0;
            width: 100%;
            height: 30%;
            background-color: rgba(79, 70, 229, 0.2);
            z-index: -1;
            transition: height 0.3s ease;
        }
        .highlight:hover:before {
            height: 40%;
        }
    </style>
</head>
<body class="bg-gray-50">
    <!-- Hero Section -->
    <section class="hero text-white py-20 px-4 md:px-0">
        <div class="container mx-auto max-w-6xl">
            <div class="flex flex-col md:flex-row items-center">
                <div class="md:w-1/2 mb-10 md:mb-0">
                    <h1 class="text-4xl md:text-5xl font-bold mb-6">RocketMQ消费位移(Offset)<br>核心概念全解析</h1>
                    <p class="text-xl mb-8 opacity-90">深入理解消息队列消费进度的关键机制</p>
                    <div class="flex space-x-4">
                        <a href="#offset-concept" class="bg-white text-indigo-600 px-6 py-3 rounded-full font-medium hover:bg-gray-100 transition">核心概念</a>
                        <a href="#storage-modes" class="border border-white text-white px-6 py-3 rounded-full font-medium hover:bg-white hover:text-indigo-600 transition">存储方式</a>
                    </div>
                </div>
                <div class="md:w-1/2">
                    <div class="relative">
                        <div class="mermaid bg-white p-6 rounded-xl shadow-xl">
                            graph LR
                            A[消费位移(Offset)] --> B[集群消费模式]
                            A --> C[广播消费模式]
                            B --> D[Broker端存储]
                            C --> E[消费者本地存储]
                            D --> F[consumerOffset.json]
                            E --> G[.rocketmq_offsets]
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- Main Content -->
    <main class="container mx-auto max-w-6xl px-4 md:px-0 py-12">
        <!-- Offset Concept Section -->
        <section id="offset-concept" class="mb-20">
            <div class="flex items-center mb-10">
                <div class="w-12 h-1 bg-indigo-500 mr-4"></div>
                <h2 class="text-3xl font-bold">消费位移(Offset)的概念</h2>
            </div>
            
            <div class="bg-white rounded-xl shadow-lg p-8 mb-10">
                <p class="text-lg mb-6">在RocketMQ中，消费位移（Offset）是指消费者消费到队列（Queue）中的哪个位置的一个标记，它记录了消费者对特定消息队列的消费进度。每个消费者组（Consumer Group）对每个消息队列（MessageQueue）都有一个消费位移，用于跟踪该消费者组在该队列上已经消费到的位置。</p>
                
                <div class="grid md:grid-cols-2 gap-8 mt-8">
                    <div class="feature-card bg-gray-50 p-6 rounded-lg border border-gray-200">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-users text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">消费者组共享</h3>
                        </div>
                        <p>以消费者组为单位进行管理，同一个消费者组下的不同消费者共享消费位移</p>
                    </div>
                    
                    <div class="feature-card bg-gray-50 p-6 rounded-lg border border-gray-200">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-database text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">队列粒度记录</h3>
                        </div>
                        <p>按照消息队列（MessageQueue）粒度记录，每个队列有独立的消费位移</p>
                    </div>
                    
                    <div class="feature-card bg-gray-50 p-6 rounded-lg border border-gray-200">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-save text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">持久化支持</h3>
                        </div>
                        <p>消费位移可以持久化，确保消费者重启后能够从上次消费的位置继续消费</p>
                    </div>
                    
                    <div class="feature-card bg-gray-50 p-6 rounded-lg border border-gray-200">
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-exchange-alt text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">提交方式灵活</h3>
                        </div>
                        <p>支持手动和自动两种提交方式，满足不同的业务需求</p>
                    </div>
                </div>
            </div>
            
            <div class="mermaid bg-white p-6 rounded-xl shadow-lg mb-10">
                flowchart TD
                    A[消费位移] --> B[消费者组1]
                    A --> C[消费者组2]
                    B --> D[队列1]
                    B --> E[队列2]
                    C --> D
                    C --> E
                    D --> F[位移值]
                    E --> G[位移值]
            </div>
        </section>
        
        <!-- Storage Modes Section -->
        <section id="storage-modes" class="mb-20">
            <div class="flex items-center mb-10">
                <div class="w-12 h-1 bg-indigo-500 mr-4"></div>
                <h2 class="text-3xl font-bold">不同消费模式下的Offset存储方式</h2>
            </div>
            
            <div class="grid md:grid-cols-2 gap-8 mb-12">
                <div class="bg-white rounded-xl shadow-lg p-8 border-t-4 border-indigo-500">
                    <div class="flex items-center mb-6">
                        <div class="w-12 h-12 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                            <i class="fas fa-server text-indigo-500 text-xl"></i>
                        </div>
                        <h3 class="text-2xl font-semibold">集群消费模式</h3>
                    </div>
                    
                    <div class="space-y-4">
                        <div class="flex items-start">
                            <i class="fas fa-map-marker-alt text-indigo-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">存储位置</h4>
                                <p class="text-gray-600">消费位移存储在Broker端的磁盘文件中($HOME/store/config/consumerOffset.json)，同时在内存中也有一份缓存</p>
                            </div>
                        </div>
                        
                        <div class="flex items-start">
                            <i class="fas fa-project-diagram text-indigo-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">存储结构</h4>
                                <p class="text-gray-600">采用Map结构存储，Key格式：{消费者组名}@{主题名}@{队列ID}，Value为消费位移值(Long类型)</p>
                            </div>
                        </div>
                        
                        <div class="flex items-start">
                            <i class="fas fa-sync-alt text-indigo-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">持久化机制</h4>
                                <p class="text-gray-600">Broker会定期(默认5秒)将内存中的消费位移数据持久化到磁盘，关闭时也会触发持久化</p>
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="bg-white rounded-xl shadow-lg p-8 border-t-4 border-purple-500">
                    <div class="flex items-center mb-6">
                        <div class="w-12 h-12 rounded-full bg-purple-100 flex items-center justify-center mr-4">
                            <i class="fas fa-desktop text-purple-500 text-xl"></i>
                        </div>
                        <h3 class="text-2xl font-semibold">广播消费模式</h3>
                    </div>
                    
                    <div class="space-y-4">
                        <div class="flex items-start">
                            <i class="fas fa-map-marker-alt text-purple-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">存储位置</h4>
                                <p class="text-gray-600">消费位移存储在消费者本地磁盘($HOME/.rocketmq_offsets/{消费者组名}/{主题名}/{broker名}/{队列ID})</p>
                            </div>
                        </div>
                        
                        <div class="flex items-start">
                            <i class="fas fa-project-diagram text-purple-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">存储结构</h4>
                                <p class="text-gray-600">每个队列的消费位移单独存储在一个文件中，文件内容为消费位移值(Long类型)</p>
                            </div>
                        </div>
                        
                        <div class="flex items-start">
                            <i class="fas fa-sync-alt text-purple-500 mt-1 mr-3"></i>
                            <div>
                                <h4 class="font-medium">持久化机制</h4>
                                <p class="text-gray-600">消费者在本地维护消费位移，重启后从本地文件加载上次消费的位置</p>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="mermaid bg-white p-6 rounded-xl shadow-lg">
                pie
                    title 消费模式使用场景
                    "集群消费" : 75
                    "广播消费" : 25
            </div>
        </section>
        
        <!-- Submission and Recovery Section -->
        <section class="mb-20">
            <div class="flex items-center mb-10">
                <div class="w-12 h-1 bg-indigo-500 mr-4"></div>
                <h2 class="text-3xl font-bold">Offset的提交和恢复机制</h2>
            </div>
            
            <div class="mb-12">
                <div class="flex items-center mb-6">
                    <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                        <i class="fas fa-paper-plane text-indigo-500"></i>
                    </div>
                    <h3 class="text-2xl font-semibold">Offset提交机制</h3>
                </div>
                
                <div class="grid md:grid-cols-3 gap-6 mb-8">
                    <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-blue-500">
                        <h4 class="font-bold text-lg mb-3">自动提交</h4>
                        <p class="text-gray-600 mb-4">消费者会定期自动提交当前消费位移，简化开发但可能导致消息重复消费或丢失</p>
                        <div class="code-block rounded-lg p-4 mb-4 overflow-x-auto">
                            <pre class="text-gray-200 text-sm">
                                <code>
// 创建消费者实例并设置为自动提交模式
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroup");
// 设置为自动提交（默认值）
consumer.setMessageModel(MessageModel.CLUSTERING);
// 设置自动提交间隔（单位：毫秒）
consumer.setPersistConsumerOffsetInterval(5000);</code>
                            </pre>
                        </div>
                    </div>
                    
                    <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-green-500">
                        <h4 class="font-bold text-lg mb-3">手动提交</h4>
                        <p class="text-gray-600 mb-4">开发者精确控制位移提交时机，通过返回CONSUME_SUCCESS或RECONSUME_LATER控制位移提交</p>
                        <div class="code-block rounded-lg p-4 mb-4 overflow-x-auto">
                            <pre class="text-gray-200 text-sm">
                                <code>
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context) {
        try {
            processMessage(msgs);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        } catch (Exception e) {
            return ConsumeConcurrentlyStatus.RECONSUME_LATER;
        }
    }
});</code>
                            </pre>
                        </div>
                    </div>
                    
                    <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-yellow-500">
                        <h4 class="font-bold text-lg mb-3">异步提交</h4>
                        <p class="text-gray-600 mb-4">精确控制批量消息中哪些消息提交位移，适用于批量消费场景</p>
                        <div class="code-block rounded-lg p-4 mb-4 overflow-x-auto">
                            <pre class="text-gray-200 text-sm">
                                <code>
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context) {
        try {
            processMessage(msgs);
            context.setAckIndex(msgs.size() - 1);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        } catch (Exception e) {
            context.setAckIndex(processedIndex);
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    }
});</code>
                            </pre>
                        </div>
                    </div>
                </div>
                
                <div class="mermaid bg-white p-6 rounded-xl shadow-lg mb-8">
                    flowchart LR
                        A[消息消费] --> B{提交方式}
                        B -->|自动提交| C[定期提交]
                        B -->|手动提交| D[业务控制]
                        B -->|异步提交| E[批量控制]
                </div>
            </div>
            
            <div>
                <div class="flex items-center mb-6">
                    <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                        <i class="fas fa-redo text-indigo-500"></i>
                    </div>
                    <h3 class="text-2xl font-semibold">Offset恢复机制</h3>
                </div>
                
                <div class="bg-white rounded-xl shadow-lg p-8 mb-8">
                    <div class="grid md:grid-cols-2 gap-8">
                        <div>
                            <h4 class="font-bold text-lg mb-4 text-indigo-600">集群模式恢复策略</h4>
                            <ul class="space-y-3">
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3 flex-shrink-0">1</span>
                                    <span><span class="font-medium">CONSUME_FROM_LAST_OFFSET</span>（默认）：从上次消费的位置开始消费</span>
                                </li>
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3 flex-shrink-0">2</span>
                                    <span><span class="font-medium">CONSUME_FROM_FIRST_OFFSET</span>：从队列的最小位移开始消费</span>
                                </li>
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3 flex-shrink-0">3</span>
                                    <span><span class="font-medium">CONSUME_FROM_TIMESTAMP</span>：从指定时间点开始消费</span>
                                </li>
                            </ul>
                        </div>
                        
                        <div>
                            <h4 class="font-bold text-lg mb-4 text-purple-600">广播模式恢复策略</h4>
                            <ul class="space-y-3">
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center mr-3 flex-shrink-0">1</span>
                                    <span>消费者启动时，首先尝试从本地文件加载消费位移</span>
                                </li>
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center mr-3 flex-shrink-0">2</span>
                                    <span>如果本地文件存在，则从记录的位置开始消费</span>
                                </li>
                                <li class="flex items-start">
                                    <span class="inline-block w-6 h-6 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center mr-3 flex-shrink-0">3</span>
                                    <span>如果本地文件不存在，则根据策略决定起始位置</span>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
                
                <div class="mermaid bg-white p-6 rounded-xl shadow-lg">
                    stateDiagram-v2
                        [*] --> 集群模式
                        集群模式 --> CONSUME_FROM_LAST_OFFSET
                        集群模式 --> CONSUME_FROM_FIRST_OFFSET
                        集群模式 --> CONSUME_FROM_TIMESTAMP
                        
                        [*] --> 广播模式
                        广播模式 --> 本地文件存在: 从文件加载
                        广播模式 --> 本地文件不存在: 策略决定
                </div>
            </div>
        </section>
        
        <!-- Best Practices Section -->
        <section>
            <div class="flex items-center mb-10">
                <div class="w-12 h-1 bg-indigo-500 mr-4"></div>
                <h2 class="text-3xl font-bold">Offset管理的最佳实践</h2>
            </div>
            
            <div class="bg-white rounded-xl shadow-lg p-8">
                <div class="grid md:grid-cols-3 gap-8">
                    <div>
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-check-circle text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">提交方式选择</h3>
                        </div>
                        <ul class="space-y-2 pl-14">
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">数据一致性高</span>：使用手动提交</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">性能要求高</span>：使用自动提交</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">批量处理</span>：使用异步提交</span>
                            </li>
                        </ul>
                    </div>
                    
                    <div>
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-play-circle text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">消费起始位置</h3>
                        </div>
                        <ul class="space-y-2 pl-14">
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">新上线应用</span>：CONSUME_FROM_LAST_OFFSET</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">处理历史数据</span>：CONSUME_FROM_FIRST_OFFSET</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span><span class="font-medium">系统恢复</span>：根据需求选择策略</span>
                            </li>
                        </ul>
                    </div>
                    
                    <div>
                        <div class="flex items-center mb-4">
                            <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-4">
                                <i class="fas fa-exclamation-triangle text-indigo-500"></i>
                            </div>
                            <h3 class="text-xl font-semibold">注意事项</h3>
                        </div>
                        <ul class="space-y-2 pl-14">
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span>避免频繁提交位移增加Broker负担</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span>消费失败返回RECONSUME_LATER让消息重新投递</span>
                            </li>
                            <li class="flex items-start">
                                <span class="inline-block w-3 h-3 rounded-full bg-indigo-500 mr-2 mt-1 flex-shrink-0"></span>
                                <span>定期检查消费进度，及时排查延迟原因</span>
                            </li>
                        </ul>
                    </div>
                </div>
                
                <div class="mt-10 p-6 bg-indigo-50 rounded-lg border border-indigo-100">
                    <div class="flex items-start">
                        <div class="flex-shrink-0">
                            <i class="fas fa-lightbulb text-indigo-500 text-2xl mr-4"></i>
                        </div>
                        <div>
                            <h4 class="text-lg font-semibold text-indigo-800 mb-2">专业建议</h4>
                            <p class="text-indigo-700">消费者数量不应超过队列数量，过多的消费者会导致部分消费者空闲。对于关键业务，建议实现幂等性处理以应对可能的重复消费情况。</p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </main>
    
    <!-- Footer -->
    <footer class="bg-gray-900 text-gray-300 py-12">
        <div class="container mx-auto max-w-6xl px-4 md:px-0">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-6 md:mb-0">
                    <h3 class="text-xl font-semibold text-white mb-2">技术小馆</h3>
                    <p class="text-gray-400">专业的开发者知识分享平台</p>
                </div>
                <div>
                    <a href="http://www.yuque.com/jtostring" class="text-indigo-300 hover:text-white transition duration-300">
                        <i class="fas fa-external-link-alt mr-2"></i>访问技术小馆
                    </a>
                </div>
            </div>
            <div class="border-t border-gray-800 mt-8 pt-8 text-center text-gray-500 text-sm">
                © 2023 技术小馆. All rights reserved.
            </div>
        </div>
    </footer>
    
    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            fontFamily: 'Noto Sans SC, sans-serif',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            },
            pie: {
                useMaxWidth: true,
                htmlLabels: true
            }
        });
    </script>
</body>
</html>